$(document).ready(function() {
    // 工具方法
    function utils () {
        return {
            /**
             * 安全字符处理
             * @param  {String} val 传入的字符，可能为undefined
             * @return {String}     返回安全字符
             */
            _escapeStr: function (val) {
                // 如果值时undefined或者null，则赋值空字符串
                val = val || ''
                return val
            },

            /**
             * 日期值格式化处理
             * @param  {String} date   输入的日期
             * @return {String} result input date控件可以识别的形如：1990-10-03
             */
            _dateFormat: function (date) {
                var result = ''
                if (date != '') {

                    // 数字字符串
                    if (!isNaN(date)) {
                        // 如果日期只有10位则以零补位
                        if (date.length == 10) date += '000'

                        // 转换为形如 “1991/1/1” 的字符串格式
                        date = new Date(parseInt(date)).toLocaleDateString()
                    }

                    // 替换非数字字符为统一的“-”
                    date = date.replace(/\D/g, '-')

                    result = dateStringFormat(date)
                }

                return result

                /**
                 * 文本日期格式化处理
                 * @param  {String} date       包含特殊字符的输入日期
                 * @return {String} formatDate input date控件可以识别的形如：1990-10-03
                 */
                function dateStringFormat(date) {
                    var formatDate = date.replace(/(\d+)/g, function (match, p1, offset, string) {

                        // 依据字符串中“-”分割字符串
                        var stringArr = string.split('-')

                        // 如果时第一组数字且字符位数等于2（年份简写，如 95 -> 1995）
                        if (match == stringArr[0] && match.length == 2) {
                            // 大等于50则为19XX，小于50则为20XX
                            match = parseInt(match) >= 50 ? '19' + match : '20' + match

                        // 月与日的位数等于1则用零补齐位数
                        } else if ((match == stringArr[1] || match == stringArr[2]) && match.length == 1) {
                            match = '0' + match
                        }

                        return match
                    })

                    return formatDate
                }
            },

            /**
             * 数字值格式化处理
             * @param  {String} number 输入的数字
             * @param  {String} min    最小值
             * @param  {String} max    最大值
             * @return {Number} number 在区间内的数字
             */
            _numberFormat: function (number, min, max) {

                number = number == '' ? 0 : parseInt(number)
                min = min == '' ? 0 : parseInt(min)
                max = max == '' ? Infinity : parseInt(max)
                if (number < min) {
                    number = 0
                } else if (number > max) {
                    number = max
                }

                return number
            },

            /**
             * html标签符号ascii转换
             * @param  {String} htmlStr html字符串
             * @return {String}         转换后的html字符串
             */
            _escapeHtml: function (htmlStr) {
                var tagsToReplace = {
                    '&': '&amp;',
                    '<': '&lt;',
                    '>': '&gt;'
                };
                return htmlStr.replace(/[&<>]/g, function(tag) {
                    return tagsToReplace[tag] || tag;
                });
            },

            /**
             * 导出html文件
             * @param  {String} filename 文件名
             * @param  {String} content  文件内容
             * @return {[type]}          [description]
             */
            _downloadFile: function (filename, content) {
                var aLink = document.createElement('a')
                var fileBlob = new Blob([content])
                var url = URL.createObjectURL(fileBlob)
                aLink.download = filename
                aLink.href = url
                aLink.click()
                URL.revokeObjectURL(url)
            },

            /**
             * 公式编译
             * @param  {String} formula    公式源码
             * @return {Object} 编译后的公式字符串, 关联控件名称数组
             */
            _formulaExec: function (formula, preview) {
                (preview != null && preview != undefined) ? preview = 1 : preview = 0
                var reg = /\{(.*?)\}/g
                var controlNames = []
                var nullFlag = 0
                var newFormula = formula.replace(reg, function (match, p1, offset, string) {
                    var controlNameAttr = preview ? '[name="' + p1 + '-preview"]' : '[name="' + p1 + '"]'
                    controlNames.push(controlNameAttr)

                    // 如果值不是数字或者为空则返回实际值 否则转换为浮点数字类型
                    return '(isNaN($(\'' + controlNameAttr + '\').val()) || $(\'' + controlNameAttr + '\').val() == \'\' ? $(\'' + controlNameAttr + '\').val() : parseFloat($(\'' + controlNameAttr + '\').val()))'
                })

                return {
                    newFormula: newFormula,
                    controlNames: controlNames,
                }
            },


            /**
             * 弹出框
             * @param  {String}    timestamp         表单编号
             * @param  {Boolean}   isShow            是否显示
             * @param  {String}    tip               内容（html）
             * @param  {Function}  okBtnCallback     确认按钮回调
             * @param  {Function}  cancelBtnCallback 取消按钮回调
             * @return {[type]}                    [description]
             */
            // modalToggle: function(timestamp, isShow, tip, okBtnCallback, cancelBtnCallback) {
            //
            //     // 参数默认值
            //     (timestamp != undefined && timestamp != null) ? timestamp = timestamp : console.error('parameter "timestamp" is required.');
            //     (isShow != undefined && isShow != null && (typeof isShow == 'boolean' || isShow == 1 || isShow == 0)) ? isShow = isShow : isShow = true;
            //     (tip != undefined && tip != null) ? tip = tip : tip = '<p></p>';
            //
            //     // 弹出框DOM对象
            //     var $modal = $('#modal-' + timestamp);
            //
            //     // 是否有取消回调，如果没有则隐藏取消按钮
            //     (cancelBtnCallback && typeof cancelBtnCallback == 'function') ? $('#cancelBtn', $modal).show() : $('#cancelBtn', $modal).hide()
            //
            //     // 回调处理
            //     $modal.on('click', '#okBtn, #cancelBtn', function(e) {
            //
            //         // 点击两个按钮都隐藏弹出框
            //         $modal.modal('hide')
            //
            //         // 触发回调
            //         if (okBtnCallback && typeof okBtnCallback == 'function' && e.target.id == 'okBtn') {
            //             okBtnCallback($modal, $(this))
            //         }
            //
            //         if (cancelBtnCallback && typeof cancelBtnCallback == 'function' && e.target.id == 'cancelBtn') {
            //             cancelBtnCallback($modal, $(this))
            //         }
            //     })
            //
            //     // 隐藏弹出框后的后续操作
            //     $modal.on('hidden.bs.modal', function () {
            //         //接触按钮事件绑定，清空弹出框内容
            //         $modal.off('click', '#okBtn, #cancelBtn')
            //               .find('.modal-body').empty()
            //     })
            //
            //     // 执行
            //     $modal.modal(isShow ? 'show' : 'hide').find('.modal-body').html(tip)
            // }
        }
    }
    // 自定义属性
    var customAttrs = {
        // 语言
        language: {
            zh: {
                addOption: '选项 +',
                allFieldsRemoved: '已移除所有控件。',
                allowSelect: '允许全选',
                allowMultipleFiles: '允许用户上传多个文件。',
                autocomplete: '自动完成',
                button: '按钮',
                cannotBeEmpty: '此处不能为空',
                checkboxGroup: '多选集',
                checkbox: '多选',
                checkboxes: '多选集',
                className: 'Class',
                clearAllMessage: '你确定要清楚所有控件吗?',
                clearAll: '清空',
                close: '关闭',
                column: '列宽',
                content: '内容',
                copy: '复制到剪贴板',
                copyButton: '&#43;',
                copyButtonTooltip: '复制',
                dateField: '时间控件',
                description: '帮助信息',
                descriptionField: '描述',
                devMode: '开发者模式',
                editNames: '编辑名称',
                editorTitle: '表单元素',
                editXML: '编辑 XML',
                enableOther: '开启 &quot;其他&quot;',
                enableOtherMsg: '允许用户选择自定义选项',
                fieldDeleteWarning: false,
                fieldVars: '控件变量',
                fieldNonEditable: '此控件不能编辑.',
                fieldRemoveWarning: '你确定要移除此控件?',
                fileUpload: '文件上传',
                formula: '公式',
                formUpdated: '表单已上传',
                getStarted: '请从右侧拖动控件到这里',
                header: '标题',
                hide: '编辑',
                hidden: '隐藏输入框',
                label: '标签',
                labelEmpty: '控件标签不能为空',
                limitRole: '以下角色限制访问:',
                mandatory: '强制性',
                maxlength: '最大长度',
                minOptionMessage: '此控件需要至少两个选项',
                multipleFiles: '多文件支持',
                name: 'Name',
                no: '否',
                number: '数字',
                off: '关',
                on: '开',
                option: '选项',
                optional: '可选的',
                optionLabelPlaceholder: '标签',
                optionValuePlaceholder: '值',
                optionEmpty: '选项值不能为空',
                other: '其他',
                paragraph: '段落',
                placeholder: '占位符',
                placeholders: {
                    value: '值',
                    label: '标签',
                    text: '',
                    textarea: '',
                    email: '请输入邮箱',
                    placeholder: '',
                    className: '多个类名用空格隔开',
                    password: '请输入密码'
                },
                preview: '预览',
                radioGroup: '单选集',
                radio: '单选',
                removeMessage: '移除元素',
                removeOption: '移除选项',
                remove: '&#215;',
                required: '必填',
                richText: '富文本编辑器',
                roles: '角色',
                save: '预览',
                selectOptions: '下拉框选项',
                select: '下拉框',
                selectColor: '选择颜色',
                selectionsMessage: '允许多选',
                size: '尺寸',
                sizes: {
                    xs: '极小',
                    sm: '小',
                    m: '默认',
                    lg: '大'
                },
                style: '样式',
                styles: {
                    btn: {
                        'default': 'Default',
                        danger: 'Danger',
                        info: 'Info',
                        primary: 'Primary',
                        success: 'Success',
                        warning: 'Warning'
                    }
                },
                subtype: '类型',
                text: '文本',
                textArea: '文本域',
                toggle: '开关',
                'data-name': '翻译名',
                warning: '警告!',
                value: '值',
                viewJSON: '{  }',
                viewXML: '&lt;/&gt;',
                yes: '是'
            }
        },

        // 列宽
        column: {
            label: 'Column',
            options: {
                '1': '1',
                '2': '2',
                '3': '3',
                '4': '4',
                '5': '5',
                '6': '6',
                '7': '7',
                '8': '8',
                '9': '9',
                '10': '10',
                '11': '11',
                '12': '12'
            },
            value: '12'
        },

        // 翻译名
        dataName: {
            label: 'Translation'
        },

        // 公式
        formula: {
            label: 'Formula'
        },
    }

    // 自定义事件
    var customEvents = {
        textEv: function (fld) {


            var $formula = $('.fld-formula', fld),
                $valueField = $('.fld-value', fld),
                $nameField = $('.fld-name', fld),
                $subtype = $('.fld-subtype', fld)

            // 公式绑定
            $formula.on('change', function (e) {

                var formula = $(this).val(),
                    nullFlag = 0
                if (formula.trim() !== '') {
                    var params = utils()._formulaExec(formula, 1)

                    if (params.controlNames.length > 0) {
                        nullFlag = params.controlNames.every(function (name) {
                            return $(name).length > 0
                        })

                       if (nullFlag) {
                           var result = eval(params.newFormula)
                           $('[name="' + $nameField.val() + '-preview"]', fld).val(result)
                           $valueField.val(result)

                           $('body').on('change', params.controlNames.toString(), function () {
                               var result = eval(params.newFormula)
                               $('[name="' + $nameField.val() + '-preview"]', fld).val(result)
                               $valueField.val(result)
                           })
                       }

                    }
                }
            })

            // 公式输入限制类型
            $subtype.on('change', function () {
                var subtype = $(this).val()
                if (subtype == 'color') {
                    $formula.val('').parents('.formula-wrap').hide()
                } else {
                    $formula.parents('.formula-wrap').show()
                }
            })

        },

        dateEv: function (fld) {
            var $value = $('.fld-value', fld)
            $value.on('change', function () {
                $(this).val(utils()._dateFormat($(this).val()))
            })
        }
    }

    // 表格属性定义
    var options = {
        messages: customAttrs.language.zh,
        disableFields: ['autocomplete', 'hidden'],
        editOnAdd: false,
        stickyControls: true,
        dataType: 'json',

        typeUserAttrs: {
            'autocomplete': { column: customAttrs.column, 'data-name': customAttrs.dataName },
            'button': { column: customAttrs.column, 'data-name': customAttrs.dataName },
            'checkbox': { column: customAttrs.column, 'data-name': customAttrs.dataName },
            'checkbox-group': { column: customAttrs.column, 'data-name': customAttrs.dataName },
            'date': { column: customAttrs.column, 'data-name': customAttrs.dataName },
            'file': { column: customAttrs.column, 'data-name': customAttrs.dataName },
            'header': { column: customAttrs.column, 'data-name': customAttrs.dataName },
            // 'hidden': { column: customAttrs.column, 'data-name': customAttrs.dataName },
            'paragraph': { column: customAttrs.column, 'data-name': customAttrs.dataName },
            'number': { column: customAttrs.column, 'data-name': customAttrs.dataName },
            'radio-group': { column: customAttrs.column, 'data-name': customAttrs.dataName },
            'select': { column: customAttrs.column, 'data-name': customAttrs.dataName },
            'text': { column: customAttrs.column, 'data-name': customAttrs.dataName, formula: customAttrs.formula },
            'textarea': { column: customAttrs.column, 'data-name': customAttrs.dataName },
        },
        typeUserEvents: {
            text: {
                onadd: customEvents.textEv,
                onclone: customEvents.textEv
            },
            date: {
                onadd: customEvents.dateEv,
                onclone: customEvents.dateEv
            }
        }
    }

    var $fbEditor = $('#fb-editor'),
        $fbPreview = $('#fb-preview')
        $fbEditorWrapper = $('.fb-editor-wrapper'),
        $fbPreviewWrapper = $('.fb-preview-wrapper'),
        formBuilder = $fbEditor.formBuilder(options).data('formBuilder')

    $('body')

        // 预览表单
        .on('click', '.form-builder-save', function () {
            $fbEditorWrapper.removeClass('active')
            $fbPreviewWrapper.addClass('active')
            // 获得表单json
            var formData = formBuilder.formData
            // console.log(formData);
            formPreviewGenerator(formData, $fbPreview)
        })

        // 返回编辑
        .on('click', '.form-builder-back', function () {
            $fbEditorWrapper.addClass('active')
            $fbPreviewWrapper.removeClass('active')
        })

        // 语言切换
        .on('click', '.form-builder-language', function () {
            var lan = $(this).data('language')
            $(this).prop('disabled', true)
                .toggleClass('btn-primary btn-default')
                .siblings().prop('disabled', false)
                .toggleClass('btn-default btn-primary')
            options.messages = customAttrs.language[lan] || {}
            formBuilder = $fbEditor.empty().formBuilder(options).data('formBuilder')
        })

        function formResources (basePath) {
            (basePath != null && basePath != undefined) ? basePath = basePath : basePath = '';

            var resources, html = ''

            if (basePath == '') {
                resources = [
                    { type: 'script', path: 'http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js' },
                    { type: 'link', path: 'http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css' },
                    { type: 'script', path: 'http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js' },
                    { type: 'link', path: 'http://cdn.bootcss.com/pnotify/3.0.0/pnotify.min.css' },
                    { type: 'link', path: 'http://cdn.bootcss.com/pnotify/3.0.0/pnotify.buttons.min.css' },
                    { type: 'link', path: 'http://cdn.bootcss.com/pnotify/3.0.0/pnotify.history.min.css' },
                    { type: 'script', path: 'http://cdn.bootcss.com/pnotify/3.0.0/pnotify.min.js' },
                    { type: 'script', path: 'http://cdn.bootcss.com/pnotify/3.0.0/pnotify.animate.min.js' },
                    { type: 'script', path: 'http://cdn.bootcss.com/pnotify/3.0.0/pnotify.buttons.min.js' },
                    { type: 'script', path: 'http://cdn.bootcss.com/pnotify/3.0.0/pnotify.callbacks.min.js' },
                    { type: 'script', path: 'http://cdn.bootcss.com/pnotify/3.0.0/pnotify.confirm.min.js' },
                    { type: 'script', path: 'http://cdn.bootcss.com/pnotify/3.0.0/pnotify.history.min.js' },
                    { type: 'script', path: 'http://cdn.bootcss.com/pnotify/3.0.0/pnotify.tooltip.min.js' },
                    { type: 'link', path: 'http://cdn.bootcss.com/bootstrap-datepicker/1.6.4/css/bootstrap-datepicker3.min.css' },
                    { type: 'script', path: 'http://cdn.bootcss.com/bootstrap-datepicker/1.6.4/js/bootstrap-datepicker.min.js' },
                    { type: 'script', path: 'http://cdn.bootcss.com/bootstrap-datepicker/1.6.4/locales/bootstrap-datepicker.zh-CN.min.js' },
                    { type: 'link', path: 'http://cdn.bootcss.com/iCheck/1.0.2/skins/square/blue.css' },
                    { type: 'script', path: 'http://cdn.bootcss.com/iCheck/1.0.2/icheck.min.js' },
                    { type: 'link', path: 'http://cdn.bootcss.com/bootstrap-colorpicker/2.3.6/css/bootstrap-colorpicker.min.css' },
                    { type: 'script', path: 'http://cdn.bootcss.com/bootstrap-colorpicker/2.3.6/js/bootstrap-colorpicker.min.js' },
                    { type: 'link', path: 'http://cdn.bootcss.com/animate.css/3.5.2/animate.min.css' },
                ]
            } else {
                resources = [
                    { type: 'script', path: basePath +  '/assets/vendors/jquery/jquery.min.js">' },
                    { type: 'link', path: basePath +  '/assets/vendors/bootstrap/css/bootstrap.min.css' },
                    { type: 'script', path: basePath +  '/assets/vendors/bootstrap/js/bootstrap.min.js' },
                    { type: 'link', path: basePath +  '/assets/vendors/pnotify/css/pnotify.min.css' },
                    { type: 'link', path: basePath +  '/assets/vendors/pnotify/css/pnotify.buttons.min.css' },
                    { type: 'link', path: basePath +  '/assets/vendors/pnotify/css/pnotify.history.min.css' },
                    { type: 'script', path: basePath +  '/assets/vendors/pnotify/js/pnotify.min.js' },
                    { type: 'script', path: basePath +  '/assets/vendors/pnotify/js/pnotify.animate.min.js' },
                    { type: 'script', path: basePath +  '/assets/vendors/pnotify/js/pnotify.buttons.min.js' },
                    { type: 'script', path: basePath +  '/assets/vendors/pnotify/js/pnotify.callbacks.min.js' },
                    { type: 'script', path: basePath +  '/assets/vendors/pnotify/js/pnotify.confirm.min.js' },
                    { type: 'script', path: basePath +  '/assets/vendors/pnotify/js/pnotify.history.min.js' },
                    { type: 'script', path: basePath +  '/assets/vendors/pnotify/js/pnotify.tooltip.min.js' },
                    { type: 'link', path: basePath + '/assets/vendors/bootstrap-datepicker/css/bootstrap-datepicker3.min.css' },
                    { type: 'script', path: basePath + '/assets/vendors/bootstrap-datepicker/js/bootstrap-datepicker.min.js' },
                    { type: 'script', path: basePath + '/assets/vendors/bootstrap-datepicker/locales/bootstrap-datepicker.zh-CN.min.js' },
                    { type: 'link', path: '/assets/vendors/icheck/css/blue.css' },
                    { type: 'script', path: '/assets/vendors/icheck/js/icheck.min.js' },
                    { type: 'link', path: '/assets/vendors/bootstrap-colorpicker/css/bootstrap-colorpicker.min.css' },
                    { type: 'script', path: '/assets/vendors/bootstrap-colorpicker/js/bootstrap-colorpicker.min.js' },
                    { type: 'link', path: basePath + '/assets/vendors/animate-css/animate.css'},
                ]
            }

            for (var i = 0; i < resources.length; i ++) {
                if (resources[i].type == 'script') {
                    html += '<script src="' + resources[i].path + '"></script>'
                } else if (resources[i].type == 'link') {
                    html += '<link rel="stylesheet" href="' + resources[i].path + '">'
                }
            }

            return html
        }

        /**
         * 表单预览生产器
         * @param  {String} jsonStr 表单结构json字符串
         * @return {String}         表单结构html
         */
        function formPreviewGenerator (jsonStr) {
            var timestamp = Date.now()
            var formId = 'form-' + timestamp,
                modalId = 'modal-' + timestamp
            var jsonObj = JSON.parse(jsonStr)
            var formHtml = [
                    '<!-- 表单部分 -->',
                    '<form class="form" id="' + formId + '" name="' + formId + '" autocomplete="off" novalidate>',
                    '<div class="row">'
                ].join('\n')

            for (var i = 0; i < jsonObj.length; i ++) {
                var htmlFormControl = [
                    '<div class="col-sm-' + utils()._escapeStr(jsonObj[i].column) + '">',
                    '<div class="form-group fb-form-group',
                    utils()._escapeStr(jsonObj[i].required) ? ' required' : '',
                    '"',
                    ' data-type="' + utils()._escapeStr(jsonObj[i].type) + '"',
                    '>',
                ].join('')
                switch (jsonObj[i].type) {
                    case 'button':
                        htmlFormControl += [
                            '<button type="' + utils()._escapeStr(jsonObj[i].subtype) + '"',
                            ' class="btn-block ' + utils()._escapeStr(jsonObj[i].className) + '"',
                            ' data-name="' + utils()._escapeStr(jsonObj[i].name) + '"',
                            ' data-value="' + utils()._escapeStr(jsonObj[i].value) + '"',
                            utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                            utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                            '>',
                            utils()._escapeStr(jsonObj[i].dataName) == '' ?  utils()._escapeStr(jsonObj[i].label) : '@Translate.TranslateString("' + utils()._escapeStr(jsonObj[i].dataName) + '")',
                            '</button>',
                        ].join('')
                        break
                    case 'checkbox':
                        htmlFormControl += [
                            '<div class="checkbox">',
                            '<label class="control-label"',
                            utils()._escapeStr(jsonObj[i].description) ?
                            ' data-description="' + utils()._escapeStr(jsonObj[i].description) + '"' : '',
                            '>',
                            '<input type="checkbox"',
                            ' class="' + utils()._escapeStr(jsonObj[i].className) + '"',
                            ' value="' + utils()._escapeStr(jsonObj[i].value) + '"',
                            ' data-name="' + utils()._escapeStr(jsonObj[i].name) + '"',
                            utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                            utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                            '>',
                            utils()._escapeStr(jsonObj[i].dataName) == '' ?  '&nbsp;' + utils()._escapeStr(jsonObj[i].label) : '&nbsp;' + '@Translate.TranslateString("' + utils()._escapeStr(jsonObj[i].dataName) + '")',
                            utils()._escapeStr(jsonObj[i].required) ? '<small class="text-danger">&nbsp;*</small>' : '',
                            utils()._escapeStr(jsonObj[i].description) ?
                            '&nbsp;<span class="text-info glyphicon glyphicon-info-sign"></span>' : '',
                            '</label>',
                            '</div>',
                        ].join('')
                        break
                    case 'checkbox-group':
                        htmlFormControl += [
                            '<label class="control-label"',
                            utils()._escapeStr(jsonObj[i].description) ?
                            ' data-description="' + utils()._escapeStr(jsonObj[i].description) + '"' : '',
                            '>',
                            utils()._escapeStr(jsonObj[i].dataName) == '' ?  utils()._escapeStr(jsonObj[i].label) : '@Translate.TranslateString("' + utils()._escapeStr(jsonObj[i].dataName) + '")',
                            utils()._escapeStr(jsonObj[i].required) ? '<small class="text-danger">&nbsp;*</small>' : '',
                            utils()._escapeStr(jsonObj[i].description) ?
                            '&nbsp;<span class="text-info glyphicon glyphicon-info-sign"></span>' : '',
                            '</label>',
                        ].join('')

                        // 遍历选项
                        for (var j = 0; j < jsonObj[i].values.length; j ++) {
                            htmlFormControl += [
                                '<div class="checkbox">',
                                '<label>',
                                '<input type="checkbox"',
                                ' class="' + utils()._escapeStr(jsonObj[i].className),
                                utils()._escapeStr(jsonObj[i].other) ? ' has-other' : '',
                                '"',
                                ' name="' + utils()._escapeStr(jsonObj[i].name) + '"',
                                ' value="' + utils()._escapeStr(jsonObj[i].values[j].value) + '"',
                                // ' data-type="checkbox-group-control"',
                                utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                                utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                                utils()._escapeStr(jsonObj[i].values[j].selected) ? ' checked': '',
                                '>',
                                '&nbsp;' + utils()._escapeStr(jsonObj[i].values[j].label),
                                '</label>',
                                '</div>',
                            ].join('')
                        }

                        // 自定义选项
                        if (utils()._escapeStr(jsonObj[i].other)) {
                            htmlFormControl += [
                                '<div class="checkbox">',
                                '<label>',
                                '<input type="checkbox"',
                                ' class="' + utils()._escapeStr(jsonObj[i].className),
                                utils()._escapeStr(jsonObj[i].other) ? ' has-other' : '',
                                ' other',
                                '"',
                                ' name="' + utils()._escapeStr(jsonObj[i].name) + '"',
                                ' value=""',
                                // ' data-type="checkbox-group-control"',
                                utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                                utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                                '>',
                                '&nbsp;其他',
                                '</label>',
                                '<input type="text" class="fb-other-input"',
                                ' name="' + utils()._escapeStr(jsonObj[i].name) + '-other"',
                                '>',
                                '</div>',
                            ].join('')
                        }

                        break
                    case 'date':
                        htmlFormControl += [
                            '<label class="control-label"',
                            utils()._escapeStr(jsonObj[i].description) ?
                            ' data-description="' + utils()._escapeStr(jsonObj[i].description) + '"' : '',
                            '>',
                            utils()._escapeStr(jsonObj[i].dataName) == '' ?  utils()._escapeStr(jsonObj[i].label) : '@Translate.TranslateString("' + utils()._escapeStr(jsonObj[i].dataName) + '")',
                            utils()._escapeStr(jsonObj[i].required) ? '<small class="text-danger">&nbsp;*</small>' : '',
                            utils()._escapeStr(jsonObj[i].description) ?
                            '&nbsp;<span class="text-info glyphicon glyphicon-info-sign"></span>' : '',
                            '</label>',
                            // '<input type="date"',
                            // ' class="' + utils()._escapeStr(jsonObj[i].className) + '"',
                            // ' name="' + utils()._escapeStr(jsonObj[i].name) + '"',
                            // ' value="' + utils()._dateFormat(utils()._escapeStr(jsonObj[i].value)) + '"',
                            // utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                            // utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                            // '>'
                            '<input type="text"',
                            ' class="' + utils()._escapeStr(jsonObj[i].className) + '"',
                            ' name="' + utils()._escapeStr(jsonObj[i].name) + '"',
                            ' value="' + utils()._dateFormat(utils()._escapeStr(jsonObj[i].value)) + '"',
                            utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                            utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                            '>'
                        ].join('')
                        break
                    case 'file':
                        htmlFormControl += [
                            '<label class="control-label"',
                            utils()._escapeStr(jsonObj[i].description) ?
                            ' data-description="' + utils()._escapeStr(jsonObj[i].description) + '"' : '',
                            '>',
                            utils()._escapeStr(jsonObj[i].dataName) == '' ?  utils()._escapeStr(jsonObj[i].label) : '@Translate.TranslateString("' + utils()._escapeStr(jsonObj[i].dataName) + '")',
                            utils()._escapeStr(jsonObj[i].required) ? '<small class="text-danger">&nbsp;*</small>' : '',
                            utils()._escapeStr(jsonObj[i].description) ?
                            '&nbsp;<span class="text-info glyphicon glyphicon-info-sign"></span>' : '',
                            '</label>',
                            '<input type="file"',
                            ' class="' + utils()._escapeStr(jsonObj[i].className) + '"',
                            ' name="' + utils()._escapeStr(jsonObj[i].name) + '"',
                            ' value=""',
                            utils()._escapeStr(jsonObj[i].multiple) ? ' multiple' : '',
                            utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                            utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                            '>'
                        ].join('')
                        break
                    case 'header':
                        htmlFormControl += [
                            '<' + utils()._escapeStr(jsonObj[i].subtype),
                            ' class="' + utils()._escapeStr(jsonObj[i].className) + '"',
                            utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                            utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                            '>',
                            utils()._escapeStr(jsonObj[i].dataName) == '' ?  utils()._escapeStr(jsonObj[i].label) : '@Translate.TranslateString("' + utils()._escapeStr(jsonObj[i].dataName) + '")',
                            '</' + utils()._escapeStr(jsonObj[i].subtype) + '>',
                        ].join('')
                        break
                    // case 'hidden':
                    //     break
                    case 'paragraph':
                        htmlFormControl += [
                            '<' + jsonObj[i].subtype,
                            ' class="' + jsonObj[i].className + '"',
                            utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                            utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                            '>',
                            utils()._escapeStr(jsonObj[i].dataName) == '' ?  utils()._escapeStr(jsonObj[i].label) : '@Translate.TranslateString("' + utils()._escapeStr(jsonObj[i].dataName) + '")',
                            '</' + jsonObj[i].subtype + '>',
                        ].join('')
                        break
                    case 'number':
                        htmlFormControl += [
                            '<label class="control-label"',
                            utils()._escapeStr(jsonObj[i].description) ?
                            ' data-description="' + utils()._escapeStr(jsonObj[i].description) + '"' : '',
                            '>',
                            utils()._escapeStr(jsonObj[i].dataName) == '' ?  utils()._escapeStr(jsonObj[i].label) : '@Translate.TranslateString("' + utils()._escapeStr(jsonObj[i].dataName) + '")',
                            utils()._escapeStr(jsonObj[i].required) ? '<small class="text-danger">&nbsp;*</small>' : '',
                            utils()._escapeStr(jsonObj[i].description) ?
                            '&nbsp;<span class="text-info glyphicon glyphicon-info-sign"></span>' : '',
                            '</label>',
                            '<input type="number"',
                            ' class="' + utils()._escapeStr(jsonObj[i].className) + '"',
                            ' name="' + utils()._escapeStr(jsonObj[i].name) + '"',
                            ' value="',
                            utils()._numberFormat(utils()._escapeStr(jsonObj[i].value), utils()._escapeStr(jsonObj[i].min), utils()._escapeStr(jsonObj[i].max)),
                            '"',
                            utils()._escapeStr(jsonObj[i].min) ? ' min="' + utils()._escapeStr(jsonObj[i].min) + '"' : '',
                            utils()._escapeStr(jsonObj[i].max) ? ' max="' + utils()._escapeStr(jsonObj[i].max) + '"' : '',
                            utils()._escapeStr(jsonObj[i].step) ? ' step="' + utils()._escapeStr(jsonObj[i].step) + '"' : '',
                            ' data-type="number"',
                            utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                            utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                            '>'
                        ].join('')
                        break
                    case 'radio-group':
                        htmlFormControl += [
                            '<label class="control-label"',
                            utils()._escapeStr(jsonObj[i].description) ?
                            ' data-description="' + utils()._escapeStr(jsonObj[i].description) + '"' : '',
                            '>',
                            utils()._escapeStr(jsonObj[i].dataName) == '' ?  utils()._escapeStr(jsonObj[i].label) : '@Translate.TranslateString("' + utils()._escapeStr(jsonObj[i].dataName) + '")',
                            utils()._escapeStr(jsonObj[i].required) ? '<small class="text-danger">&nbsp;*</small>' : '',
                            utils()._escapeStr(jsonObj[i].description) ?
                            '&nbsp;<span class="text-info glyphicon glyphicon-info-sign"></span>' : '',
                            '</label>',
                        ].join('')

                        // 遍历选项
                        for (var k = 0; k < jsonObj[i].values.length; k ++) {
                            htmlFormControl += [
                                '<div class="radio">',
                                '<label>',
                                '<input type="radio"',
                                ' class="' + utils()._escapeStr(jsonObj[i].className),
                                utils()._escapeStr(jsonObj[i].other) ? ' has-other' : '',
                                '"',
                                ' name="' + utils()._escapeStr(jsonObj[i].name) + '"',
                                ' value="' + utils()._escapeStr(jsonObj[i].values[k].value) + '"',
                                // ' data-type="radio-group-control"',
                                utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                                utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                                utils()._escapeStr(jsonObj[i].values[k].selected) ? ' checked': '',
                                '>',
                                '&nbsp;' + utils()._escapeStr(jsonObj[i].values[k].label),
                                '</label>',
                                '</div>',
                            ].join('')
                        }

                        // 自定义选项
                        if (utils()._escapeStr(jsonObj[i].other)) {
                            htmlFormControl += [
                                '<div class="radio">',
                                '<label>',
                                '<input type="radio"',
                                ' class="' + utils()._escapeStr(jsonObj[i].className),
                                utils()._escapeStr(jsonObj[i].other) ? ' has-other' : '',
                                ' other',
                                '"',
                                ' name="' + utils()._escapeStr(jsonObj[i].name) + '"',
                                ' value=""',
                                // ' data-type="radio-group-control"',
                                utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                                utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                                '>',
                                '&nbsp;其他',
                                '</label>',
                                '<input type="text" class="fb-other-input"',
                                ' name="' + utils()._escapeStr(jsonObj[i].name) + '-other"',
                                '>',
                                '</div>',
                            ].join('')
                        }
                        break
                    case 'select':
                        htmlFormControl += [
                            '<label class="control-label"',
                            utils()._escapeStr(jsonObj[i].description) ?
                            ' data-description="' + utils()._escapeStr(jsonObj[i].description) + '"' : '',
                            '>',
                            utils()._escapeStr(jsonObj[i].dataName) == '' ?  utils()._escapeStr(jsonObj[i].label) : '@Translate.TranslateString("' + utils()._escapeStr(jsonObj[i].dataName) + '")',
                            utils()._escapeStr(jsonObj[i].required) ? '<small class="text-danger">&nbsp;*</small>' : '',
                            utils()._escapeStr(jsonObj[i].description) ?
                            '&nbsp;<span class="text-info glyphicon glyphicon-info-sign"></span>' : '',
                            '</label>',
                        ].join('')

                        htmlFormControl += [
                            '<select class="' + utils()._escapeStr(jsonObj[i].className) + '"',
                            utils()._escapeStr(jsonObj[i].multiple) ? ' multiple' : '',
                            ' name="' + utils()._escapeStr(jsonObj[i].name) + '"',
                            utils()._escapeStr(jsonObj[i].placeholder) ? ' placeholder="' + utils()._escapeStr(jsonObj[i].placeholder) + '"' : '',
                            utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                            utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                            '>',
                            utils()._escapeStr(jsonObj[i].placeholder) ? '<option disabled selected>' + utils()._escapeStr(jsonObj[i].placeholder) + '</option>' : '',
                        ].join('')

                        // 遍历选项
                        for (var v = 0; v < jsonObj[i].values.length; v ++) {
                            htmlFormControl += [
                                '<option value="' + utils()._escapeStr(jsonObj[i].values[v].value) + '">',
                                utils()._escapeStr(jsonObj[i].values[v].label),
                                '</option>',
                            ].join('')
                        }

                        htmlFormControl += [
                            '</select>'
                        ].join('')

                        break
                    case 'text':
                        htmlFormControl += [
                            '<label class="control-label"',
                            utils()._escapeStr(jsonObj[i].description) ?
                            ' data-description="' + utils()._escapeStr(jsonObj[i].description) + '"' : '',
                            '>',
                            utils()._escapeStr(jsonObj[i].dataName) == '' ?  utils()._escapeStr(jsonObj[i].label) : '@Translate.TranslateString("' + utils()._escapeStr(jsonObj[i].dataName) + '")',
                            utils()._escapeStr(jsonObj[i].required) ? '<small class="text-danger">&nbsp;*</small>' : '',
                            utils()._escapeStr(jsonObj[i].description) ?
                            '&nbsp;<span class="text-info glyphicon glyphicon-info-sign"></span>' : '',
                            '</label>',
                            utils()._escapeStr(jsonObj[i].subtype) == 'color' ? '<div class="input-group">' : '',
                            '<input type="' + utils()._escapeStr(jsonObj[i].subtype) + '"',
                            ' class="' + utils()._escapeStr(jsonObj[i].className) + '"',
                            ' name="' + utils()._escapeStr(jsonObj[i].name) + '"',
                            ' value="' + utils()._escapeStr(jsonObj[i].value) + '"',
                            utils()._escapeStr(jsonObj[i].placeholder) ? ' placeholder="' + utils()._escapeStr(jsonObj[i].placeholder) + '"' : '',
                            utils()._escapeStr(jsonObj[i].maxlength) ? ' maxlength="' + utils()._escapeStr(jsonObj[i].maxlength) + '"' : '',
                            utils()._escapeStr(jsonObj[i].formula) ? ' disabled' : '',
                            utils()._escapeStr(jsonObj[i].formula) ? ' data-formula="' + utils()._escapeStr(jsonObj[i].formula) + '"' : '',
                            utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                            utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                            '>',
                            utils()._escapeStr(jsonObj[i].subtype) == 'color' ? '<span class="input-group-addon"><i></i></span></div>' : '',
                        ].join('')
                        break
                    case 'textarea':
                        htmlFormControl += [
                            '<label class="control-label">',
                            utils()._escapeStr(jsonObj[i].dataName) == '' ?  utils()._escapeStr(jsonObj[i].label) : '@Translate.TranslateString("' + utils()._escapeStr(jsonObj[i].dataName) + '")',
                            utils()._escapeStr(jsonObj[i].required) ? '<small class="text-danger">&nbsp;*</small>' : '',
                            utils()._escapeStr(jsonObj[i].description) ?
                            '&nbsp;<span class="text-info glyphicon glyphicon-info-sign"></span>' : '',
                            '</label>',
                            '<textarea class="' + utils()._escapeStr(jsonObj[i].className) + '"',
                            ' name="' + jsonObj[i].name + '"',
                            utils()._escapeStr(jsonObj[i].rows) ? ' rows="' + utils()._escapeStr(jsonObj[i].rows) + '"' : '',
                            utils()._escapeStr(jsonObj[i].maxlength) ? ' maxlength="' + utils()._escapeStr(jsonObj[i].maxlength) + '"' : '',
                            utils()._escapeStr(jsonObj[i].placeholder) ? ' placeholder="' + utils()._escapeStr(jsonObj[i].placeholder) + '"' : '',
                            utils()._escapeStr(jsonObj[i].access) ? ' data-access="' + utils()._escapeStr(jsonObj[i].access) + '"' : '',
                            utils()._escapeStr(jsonObj[i].role) ? ' data-role="' + utils()._escapeStr(jsonObj[i].role) + '"' : '',
                            '>' + utils()._escapeStr(jsonObj[i].value) + '</textarea>',
                        ].join('')
                        break
                }

                htmlFormControl += '</div></div>'
                formHtml += htmlFormControl
            }

            formHtml += [
                '</div>',
                '</form>',
            ].join('\n')

            $fbPreview.html(formHtml)

            // 测试表单提交按钮
            $('#formSubmitBtn').off('click').on('click', function () {
                // console.log('submit start!');
                formUtils().formSubmit(timestamp, function (formData, $form) {
                    console.log(formData, $form)
                })
            })

            // 自定义资源路径按钮
            $('#formResourcesBtn').on('click', function () {
                if ($(this).hasClass('active')) {
                    $(this).removeClass('active btn-success').addClass('btn-danger').find('.glyphicon').addClass('glyphicon-ban-circle').removeClass('glyphicon-ok-circle')
                    $('#formResourcesInput').prop('disabled', true).val('')
                } else {
                    $(this).addClass('active btn-success').removeClass('btn-danger').find('.glyphicon').removeClass('glyphicon-ban-circle').addClass('glyphicon-ok-circle')
                    $('#formResourcesInput').prop('disabled', false)
                }
            })

            // 表单html查看按钮
            $('#formHtmlWatchBtn').off('click').on('click', function () {
                var basePath = $('#formResourcesInput').val()
                var formHtmlSkeleton = [
                    '<!DOCTYPE html>',
                    '<html lang="zh">',
                    '<head>',
                    '<meta charset="UTF-8">',
                    '<meta name="viewport" content="width=device-width, initial-scale=1.0">',
                    '<meta http-equiv="X-UA-Compatible" content="ie=edge">',
                    '<!-- scripts & stylesheet -->',
                    formResources(basePath),
                    '<style>',
                    'body{ font-family: "微软雅黑", "Microsoft Yahei", "Helvetica Neue", Helvetica, STHeiTi, sans-serif; }',
                    '.fb-other-input{display:none;border:none;border-bottom:1px solid #555;margin:0 0.5em;padding:0 0.5em;width:6em}',
                    '.fb-other-input.active{display:inline-block}',
                    '.fb-other-input:focus{outline:0}',
                    '</style>',
                    '<title>表单：' + timestamp + '</title>',
                    '</head>',
                    '<body>',
                    '<div class="container">',
                    '<button class="btn btn-sm btn-success" id="formSubmitTestBtn">测试提交</button><br>',
                    formHtml,
                    '</div>',
                    '<script>',
                    '$(document).ready(function() {',
                    'formUtils().formCreated(' + timestamp + ')',
                    '$(\'#formSubmitTestBtn\').on(\'click\', function (e) {',
                    'formUtils().formSubmit(' + timestamp + ', function(formData, $form) { console.log(\'Submit!\') })',
                    '})',
                    formUtils.toString(),
                    utils.toString(),
                    '})',
                    '</script>',
                    '</body>',
                    '</html>'
                ].join('\n')

                $('#formHtmlWatchModal').modal('show').find('.modal-body').html([
                    '<pre><code>',
                    utils()._escapeHtml(formHtmlSkeleton),
                    '</code></pre>'
                ].join('\n'))

                // 代码高亮初始化
                $('pre code').each(function(i, block) {
                    hljs.highlightBlock(block);
                });

                // 导出为html
                $('#formHtmlExportBtn').off('click').on('click', function () {
                    console.log(123);
                    utils()._downloadFile(formId + '.html', [formHtmlSkeleton])
                })
            })

            // ---- 组件激活 ----
            formUtils().formCreated(timestamp)
        }

        function formUtils () {
            return {
                /**
                 * 表单创建后执行项目
                 * @param  {Number} timestamp 时间戳（毫秒）
                 * @return {[type]}           [description]
                 */
                formCreated: function (timestamp) {
                    var $form = $('#form-' + timestamp)

                    // PNotify 样式调整为bootstrap
                    PNotify.prototype.options.styling = "bootstrap3"

                    // 工具提示，基于PNotify
                    function tooltip (text) {
                        var tooltip = new PNotify({
                            title: '帮助信息',
                            text: text,
                            hide: false,
                            type: 'info',
                            buttons: {
                                closer: false,
                                sticker: false
                            },
                            history: {
                                history: false
                            },
                            animate_speed: 'fast',
                            opacity: 0.9,
                            icon: 'glyphicon glyphicon-info-sign',
                            stack: false,
                            auto_display: false
                        })

                        tooltip.get().mouseout(function () {
                            tooltip.remove()
                        })

                        return tooltip
                    }

                    $('[data-description]').each(function (index, elem) {
                        var description = $(elem).data('description')

                        var tt = tooltip(description)

                        $(elem)
                            .on('mouseover', function () {
                                tt.open()
                            })
                            .on('mousemove', function () {
                                tt.get().css({
                                    'top': event.clientY + 12,
                                    'left': event.clientX + 12
                                });
                            })
                            .on('mouseout', function () {
                                tt.remove()
                            })
                            .on('click', function () {
                                new PNotify({
                                    title: '帮助信息',
                                    text: description,
                                    type: 'info',
                                    icon: 'glyphicon glyphicon-info-sign'
                                })
                            })
                    })

                    // datepicker控件
                    $('[data-type="date"] input').datepicker({
                        format: "yyyy-mm-dd",
                        todayBtn: 'linked',
                        language: 'zh-CN',
                        todayHighlight: true
                    })

                    // icheck控件
                    $('[data-type="checkbox"], [data-type="checkbox-group"], [data-type="radio-group"]').iCheck({
                        checkboxClass: 'icheckbox_square-blue',
                        radioClass: 'iradio_square-blue',
                        increaseArea: '20%'
                    })

                    // colorpicker控件
                    $('[type="color"]').attr('type', 'text').parent().colorpicker({
                    })

                    $form
                        // 自定义选项
                        .on('ifChanged', '.has-other', function () {
                        var that = this
                        var $other = $('.other[name="' + $(that).attr('name') + '"]')
                        var $otherInput = $other.parents('label').next()
                        // 如果已选中
                        if ($other.prop('checked')) {

                            // 激活自定义输入框并且输入时更新checkbox的值
                            $otherInput.addClass('active')
                            $otherInput.on('input', function () {
                                $other.val($(this).val())
                            })

                        // 如果未勾选
                        } else {

                            // 清空并隐藏自定义输入框，解绑输入事件
                            $otherInput.val('').removeClass('active').off('input')
                            // 清空checkbox的值
                            $other.val('')
                        }
                    })

                    // 数字输入框
                    .on('input paste', '[data-type="number"]', function () {
                        var number = $(this).val(),
                            min = $(this).attr('min'),
                            max = $(this).attr('max')

                        // 如果是非数字字符
                        if (/\D+/g.test(number)) {
                            $(this).val('') // 清空
                        } else {
                            $(this).val(utils()._numberFormat(number, min, max)) // 取最小值或者最大值
                        }
                    })

                    // 公式关联控件绑定
                    $('[data-formula]').each(function (index, elem) {
                        var formula = $(elem).data('formula'),
                            noNullFlag = 0

                        var params = utils()._formulaExec(formula)

                        if (params.controlNames.length > 0) {
                                noNullFlag = params.controlNames.every(function (name) {
                                    return $(name).length > 0
                                })

                                if (noNullFlag) {
                                    // 执行公式
                                    var result = eval(params.newFormula)

                                    // 值为 undefined 或者 null 或者 NaN
                                    if (result == undefined || result == null || (isNaN(result) && typeof result == 'number')) {
                                        $(elem).val('')
                                    } else {
                                        $(elem).val(result)
                                    }

                                    $form.on('change', params.controlNames.toString(),   function () {
                                        var result = eval(params.newFormula)
                                        if (result == undefined || result == null || (isNaN(result) && typeof result == 'number')) {
                                            $(elem).val('')
                                        } else {
                                            $(elem).val(result)
                                        }
                                   })
                                }
                            }

                    })



                },

                /**
                 * 表单验证并提交
                 * @param  {String}          timestamp 表单编号
                 * @param  {Function|String} callback  确认提交回调 | 提交地址
                 * @return {[type]}          [description]
                 */
                formSubmit: function (timestamp, callback) {
                    var formName = 'form-' + timestamp,
                        $form = $('#' + formName)

                    // 错误码
                    var errorCode = 0

                    // 必填校验
                    $('.fb-form-group.required', $form).removeClass('has-error').each(function (idx, elem) {
                        var type = $(elem).data('type')
                        var $control
                        switch (type) {
                            // 单选控件
                            case 'checkbox':
                                $control = $(elem).find('input[type="' + type + '"]')
                                if ($control.prop('checked') == false) {
                                    $(elem).addClass('has-error')
                                    errorCode = 1
                                }
                                break
                            // 单输入控件
                            case 'select':
                            case 'date':
                            case 'text':
                            case 'textarea':
                                $control = $(elem).find('input[type="text"], input[type="password"], input[type="email"], input[type="color"], input[type="tel"], select, textarea')
                                if (!$control.val() || $control.val().trim() == '') {
                                    $(elem).addClass('has-error')
                                    errorCode = 1
                                }
                                break

                            // 多选控件
                            case 'checkbox-group':
                                $control = $(elem).find('[data-type="' + type + '"]:checked')
                                if ($control.length == 0) {
                                    $(elem).addClass('has-error')
                                    errorCode = 1
                                }
                                break
                            case 'file':
                                $control = $(elem).find('input[type="' + type + '"]')
                                if ($control[0].files.length == 0) {
                                    $(elem).addClass('has-error')
                                    errorCode = 1
                                }
                                break
                        }
                    })

                    // 公式校验
                    $('[data-formula]', $form).each(function (index, elem) {
                        var val = $(elem).val(),
                            $formGroup = $(elem).parent('.fb-form-group')

                        $formGroup.removeClass('has-error')

                        if (val == '' || (isNaN(val) && typeof val == 'number')) {
                            errorCode = 2
                            $formGroup.addClass('has-error')
                        }
                    })

                    // 根据错误码弹出提示框
                    switch (errorCode) {
                        case 1:
                            // 遗漏必填项
                            new PNotify({
                                title: '遗漏必填项  (errorCode: 1)',
                                text: '标红项为必填选项，请填写完成后重新提交。',
                                type: 'error',
                                icon: 'glyphicon glyphicon-asterisk'
                            })
                            break
                        case 2:
                            // 关联项内容不正确
                            new PNotify({
                                title: '关联项内容不正确 (errorCode: 2)',
                                text: '该项由关联项内容决定，请检查关联项内容后重新提交。',
                                type: 'error',
                                icon: 'glyphicon glyphicon-link'
                            })
                            break
                        default:
                            // 提交确认
                            new PNotify({
                                title: '表单提交确认',
                                text: '确认提交该表单？',
                                // type: 'info',
                                icon: 'glyphicon glyphicon-question-sign',
                                animate: {
                                    animate: true,
                                    in_class: 'bounceIn',
                                    out_class: 'bounceOut'
                                },
                                hide: false,
                                confirm: {
                                    confirm: true
                                },
                                buttons: {
                                    closer: false,
                                    sticker: false
                                },
                                history: {
                                    history: false
                                },
                                addclass: 'stack-modal',
                                stack: {
                                    'dir1': 'down',
                                    'dir2': 'right',
                                    'modal': true
                                }
                            }).get().on('pnotify.confirm', function () {
                                console.log('go!');
                                if (callback) {
                                    if (typeof callback == 'function') {
                                        var formData = new FormData(formName)
                                        callback(formData, $form)
                                    } else if (typeof callback == 'string') {
                                        $form.attr('action', callback)
                                        $form.submit()
                                    }
                                } else {
                                    console.error('parameter callback is required. (Function or URL path)')
                                }

                            }).on('pnotify.cancel', function () {
                                console.log('stop!');
                            })
                    }
                },
            }
        }
})
